Option Explicit

'################################################################
' Descriptor
' 字符串前缀表 基础实现
'################################################################

Implements IStringNextTable

'################################################################
' Field
'################################################################
' 原始字符串
Private originStr As String

' 原始字符串的长度
Private strLength As Integer

' 原始字符串的字符数组
' 用来在比较字符时进行加速，以避免重复使用 Mid 来截取字符
Private strArray() As String

' 前缀表
Private nextTable() As Integer

' 活动索引
Private fieldActiveIndex As Integer

'################################################################
' Init
'################################################################
Public Sub Init(ByRef str As String)
    ' 1. 检查参数
    If str = "" Then
        Err.Raise 10007, Description:="String parameter[str] must not be empty string."
        Exit Sub
    End If

    ' 2. 保存基础参数
    originStr = str
    strLength = Len(originStr)

    ' 3. 生成字符串的字符数组
    ReDim strArray(0 To (strLength - 1))
    Dim i As Integer
    For i = 0 To (strLength - 1)
        strArray(i) = Mid(str, i + 1, 1)
    Next

    ' 4. 为当前字符串生成前缀表
    ReDim nextTable(0 To (strLength - 1))
    Dim j As Integer
    j = 0
    nextTable(0) = j
    For i = 1 To (strLength - 1)
        While j > 0 And strArray(i) <> strArray(j)
            j = nextTable(j - 1)
        Wend

        If strArray(i) = strArray(j) Then
            j = j + 1
        End If

        nextTable(i) = j
    Next

    ' 5. 将活动索引定位到第一个字符
    fieldActiveIndex = 0
End Sub

'################################################################
' Property
'################################################################
' 返回各个字符串前缀表的原始字符串
Public Property Get IStringNextTable_OriginStr() As String
    IStringNextTable_OriginStr = originStr
End Property

' 获取各个实现类的标准化后的字符串
Public Property Get IStringNextTable_Str() As String
    IStringNextTable_Str = originStr
End Property

' 获取原始字符串的长度
Public Property Get IStringNextTable_Length() As Integer
    IStringNextTable_Length = strLength
End Property

' 获取原始字符串
Public Property Get IStringNextTable_ActiveIndex() As Integer
    IStringNextTable_ActiveIndex = fieldActiveIndex
End Property

'################################################################
' Function
'################################################################
' 检查当前字符串是否是以 s 对象中的字符串开始的
' @param s 需要检查的字符串
Public Function IStringNextTable_startwith(ByRef s As IStringNextTable) As Boolean
    If strLength < s.Length Then
        IStringNextTable_startwith = False
    ElseIf strLength = s.Length Then
        If TypeOf s Is IgnoreCaseStrNextTable Then
            IStringNextTable_startwith = ( LCase(originStr) = s.Str )
        Else
            IStringNextTable_startwith = ( originStr = s.Str )
        End If
    Else
        If TypeOf s Is IgnoreCaseStrNextTable Then
            ' 以保证 ABC(忽略大小写) 是 aBcD 的开头
            IStringNextTable_startwith = ( LCase(Left(originStr, s.Length)) = s.Str )
        Else
            IStringNextTable_startwith = ( Left(originStr, s.Length) = s.Str )
        End If
    End If
End Function

' 检查当前字符串是否是以 s 字符串开始的
'
' @param s, 需要检查的字符串
Public Function IStringNextTable_startwithStr(ByRef s As String) As Boolean
    IStringNextTable_startwithStr = ( Left(originStr, Len(s)) = s )
End Function

' 检查当前字符串是否是以 s 对象中的字符串结束的
'
' @param s, 需要检查的字符串
Public Function IStringNextTable_endwith(ByRef s As IStringNextTable) As Boolean
    If strLength < s.Length Then
        IStringNextTable_endwith = False
    ElseIf strLength = s.Length Then
        If TypeOf s Is IgnoreCaseStrNextTable Then
            IStringNextTable_endwith = ( LCase(originStr) = s.Str )
        Else
            IStringNextTable_endwith = ( originStr = s.Str )
        End If
    Else
        If TypeOf s Is IgnoreCaseStrNextTable Then
            ' 以保证 ABC(忽略大小写) 是 aBcD 的开头
            IStringNextTable_endwith = ( LCase(Right(originStr, s.Length)) = s.Str )
        Else
            IStringNextTable_endwith = ( Right(originStr, s.Length) = s.Str )
        End If
    End If
End Function

' 检查当前字符串是否是以 s 对象中的字符串结束的
'
' @param s, 需要检查的字符串
Public Function IStringNextTable_endwithStr(ByRef s As String) As Boolean
    IStringNextTable_endwithStr = ( Right(originStr, Len(s)) = s )
End Function

' 获取活动索引上的字符
Public Function IStringNextTable_getActiveChar() As String
    IStringNextTable_getActiveChar = strArray(fieldActiveIndex)
End Function

' 手动设置活动索引
' @param index 活动索引
Public Sub IStringNextTable_setActiveIndex(ByRef index As Integer)
    fieldActiveIndex = index
End Sub

' 推进到下一个活动索引
Public Sub IStringNextTable_toNextActiveIndex()
    fieldActiveIndex = fieldActiveIndex + 1
End Sub

' 当前的活动和指定字符进行比较
' @param c 需要进行比较的字符串
Public Sub IStringNextTable_matchChar(ByRef c As String)
    While fieldActiveIndex > 0 And strArray(fieldActiveIndex) <> c
        fieldActiveIndex = nextTable(fieldActiveIndex - 1)
    Wend

    If strArray(fieldActiveIndex) = c Then
        fieldActiveIndex = fieldActiveIndex + 1
    End If
End Sub

' 当前的活动和指定字符进行比较，并返回比较结果
' 只会执行字符比较，不会修改活动索引
'
' @param c 需要进行比较的字符串
Public Function IStringNextTable_equalActiveChar(ByRef c As String) As Boolean
    IStringNextTable_equalActiveChar = (strArray(fieldActiveIndex) = c)
End Function

' 检查当前字符串是否已经完成匹配
Public Function IStringNextTable_isWholeMatched() As Boolean
    IStringNextTable_isWholeMatched = (fieldActiveIndex = strLength)
End Function

' 重置上下文相关内容
Public Sub IStringNextTable_reset()
    fieldActiveIndex = 0
End Sub
